import * as THREE from 'three';
import _ from 'lodash';
import Base from './Base.js';
import Stats from 'three/examples/jsm/libs/stats.module.js';
import Sprite from './Sprite.js';
import Point from '../../Point.js';
import Gaffer from './Gaffer.js';
import Storage from '../../Storage.js';
import { Sky } from 'three/examples/jsm/objects/Sky.js';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js';
import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js';
import { UnrealBloomPass } from 'three/examples/jsm/postprocessing/UnrealBloomPass.js';
import { OutlinePass } from 'three/examples/jsm/postprocessing/OutlinePass.js';
import { BoxGeometry, Mesh, MeshStandardMaterial } from "three"

let ARTIST_3D_INSTANCE = null;
class Artist extends Base {
    constructor(root, viewport) {
        super();
        this.root = root;
        this.viewport = viewport;

        this.scene = null;
        this.currentScene = null;
        this.camera = null;
        this.defaultCamera = null;
        this.objects = [];

        this.composer = null;
        this.plane = null;

        this.clock = new THREE.Clock();
        // this.initPointerSprite();
        ARTIST_3D_INSTANCE = this;
    }

    static getInstance(root, viewport) {
        if (ARTIST_3D_INSTANCE) return ARTIST_3D_INSTANCE;
        else return (ARTIST_3D_INSTANCE = new Artist(root, viewport));
    }

    static caculateViewport(el) {
        let rtn = { el, width: 0, height: 0, ratio: 2, x: 0, y: 0, pointer: { x: 0, y: 0, z: 0 } };
        if (!el) return rtn;
        let o = getComputedStyle(el),
            w = parseInt(o.width),
            h = parseInt(o.height);
        rtn.width = w;
        rtn.height = h;
        return rtn;
    }

    init(args) {
        let {
            container,
            offset,
            scene,
            camera,
            light,
            showCameraHelper,
            sky,
            floor,
            axesHelper,
            gridHelper,
            showStats,
            showGUI,
            controlsHelper,
            effect
        } = args;
        this.initScene(scene);
        this.initCamera(camera);
        this.initLight(light, showCameraHelper);
        this.initSky(sky);
        this.initFloor(floor);
        this.initAxes(axesHelper);
        this.initGrid(gridHelper);
        this.initControls(controlsHelper);
        this.initStats(showStats);
        this.initEffect(effect);
    }

    initScene(scene = {}) {
        let { background = 0xf5f5f5, fog = 0xcccccc, density = 0.0002 } = scene;
        // console.log('Artist.initScene: ', scene);
        this.scene = new THREE.Scene();
        this.scene.background = new THREE.Color(background);
        this.scene.fog = new THREE.FogExp2(fog, density);
        this.pushScene(this.scene);
        return this.scene;
    }
    initStats(showStats) {
        if (!showStats) return;
        if (!this.viewport || !this.viewport.el) return;
        this.stats = new Stats();
        this.viewport.el.appendChild(this.stats.dom);
    }
    initCamera(camera = {}) {
        let { x = -50, y = 30, z = 70, lx = 0, ly = 0, lz = 0, helper } = camera;
        if (!this.defaultCamera) this.defaultCamera = camera;
        console.log('Artist.initCamera: ', camera);
        this.scene.rotation.set(0, 0, 0);
        let viewType = Storage.getSessionStorage('TINYENGINE').get('SETTINGS').viewType || '3D',
            s = 150,
            k = this.viewport.width / this.viewport.height;
        if (viewType === '2D') this.camera = new THREE.OrthographicCamera(-s * k, s * k, s, -s, 1, 1000); //eslint-disable-line
        else this.camera = new THREE.PerspectiveCamera(90, k, 1, 10000); //eslint-disable-line
        // this.scene.rotation.x = 0;
        // this.scene.rotation.y = 0;
        // this.scene.rotation.z = 0;
        // console.warn('===1===> ', this.camera.rotation);
        if (viewType === '2D') this.camera.position.set(0, 500, 0);
        else this.camera.position.set(x, y, z);
        this.camera.lookAt(new THREE.Vector3(0, 0, 0));

        if (this.controls) this.controls.target.set(lx, ly, lz);

        if (this.camera && helper && !this.cameraHelper) {
            this.cameraHelper = new THREE.CameraHelper(this.camera);
            this.scene.add(this.cameraHelper);
        } else if (!helper && this.cameraHelper) {
            this.scene.remove(this.cameraHelper);
            this.cameraHelper = null;
        }
        return this.camera;
    }
    initLight(args = {}, showCameraHelper) {
        let { skyColor = '#ffffff', groundColor, intensity = 0.75, hIntensity, pos, shadow } = args;

        // console.log('[Artist] ', this.light, shadow);
        // if (!this.light && !shadow) this.light = Gaffer.getDirectionalLight(args);
        // else if (!this.light) this.light = Gaffer.getSpotLight(args);
        // this.light.position.set(pos.x, pos.y, pos.z);
        // this.light.color.set(skyColor);
        // this.light.intensity = intensity;
        // if (shadow && this.light.shadow && this.light.shadow.camera && this.light.shadow.mapSize) {
        //     this.light.shadow.camera.far = shadow.far;
        //     this.light.shadow.mapSize.width = shadow.width;
        //     this.light.shadow.mapSize.height = shadow.height;
        // }
        this.light = Gaffer.getDirectionalLight(args);
        this.light.position.set(pos.x, pos.y, pos.z);
        // this.light.position.multiplyScalar(1.3);
        this.light.color.set(skyColor);
        this.light.intensity = intensity;
        if (shadow && this.light.shadow && this.light.shadow.camera && this.light.shadow.mapSize) {
            this.light.castShadow = true;
            this.light.shadow.camera.far = shadow.far;
            this.light.shadow.mapSize.width = shadow.width;
            this.light.shadow.mapSize.height = shadow.height;
            const d = 300;
            this.light.shadow.camera.left = -d;
            this.light.shadow.camera.right = d;
            this.light.shadow.camera.top = d;
            this.light.shadow.camera.bottom = -d;
            this.light.shadow.camera.far = 1000;
        }

        if (!this.backlit && pos) {
            // let backlit = { ...args };
            let backlit = _.cloneDeep(args);
            backlit.castShadow = false;
            backlit.pos.x = -args.pos.x;
            backlit.pos.y = -args.pos.y;
            this.backlit = Gaffer.getDirectionalLight(backlit);
        }

        if (!this.hLight && (groundColor || hIntensity)) this.hLight = Gaffer.getHemisphereLight(args);
        this.hLight.intensity = hIntensity;
        if (this.hLight.groundColor) this.hLight.groundColor.set(groundColor);

        // return { light: this.light, bLight: this.backlit, hLight: this.hLight };
        this.scene.add(this.light);
        this.scene.add(this.backlit);
        this.scene.add(this.hLight);

        if (args.shadow && showCameraHelper) this.initCameraHelper(args);
    }
    initCameraHelper() {
        if (!this.light || !this.light.shadow) return;
        this.shadowCameraHelper = new THREE.CameraHelper(this.light.shadow.camera);
        this.shadowCameraHelper.visible = true;
        this.scene.add(this.shadowCameraHelper);
    }
    initSky(sky = {}) { //eslint-disable-line
        if (sky === null) sky = {};
        let {
            enable = false,
            turbidity = 10,
            rayleigh = 0.8,
            mieCoefficient = 0.005,
            mieDirectionalG = 0.8,
            exposure = 0.5,
            inclination = 0,
            azimuth = 0.25
        } = sky;
        if (!enable) {
            if (this.sky) this.scene.remove(this.sky);
            return (this.sky = null);
        }

        if (!inclination) inclination = 0;

        if (!this.sky) {
            this.sky = new Sky();
            this.sky.name = 'Sky';
            this.sky.scale.setScalar(500000);
            this.sun = new THREE.Vector3();
        }

        this.sky.material.uniforms.turbidity.value = turbidity;
        this.sky.material.uniforms.rayleigh.value = rayleigh;
        this.sky.material.uniforms.mieCoefficient.value = mieCoefficient;
        this.sky.material.uniforms.mieDirectionalG.value = mieDirectionalG;

        let theta = Math.PI * (inclination - 0.5),
            phi = 2 * Math.PI * (azimuth - 0.5);
        this.sun.x = Math.cos(phi);
        this.sun.y = Math.sin(phi) * Math.sin(theta);
        this.sun.z = Math.sin(phi) * Math.cos(theta);
        this.sky.material.uniforms.sunPosition.value.copy(this.sun);

        if (this.root && this.root.renderer) this.root.renderer.toneMappingExposure = exposure;
        // this.root.renderer.render(this.currentScene, this.camera);
        // return this.sky;
        this.scene.add(this.sky);
    }
    initFloor(args) {
        if (!args) return;
        let { width, height, color } = args;
        var geometry = new THREE.PlaneGeometry(width, height);
        geometry.rotateX(-Math.PI / 2);
        if (!color) {
            color = 0xffffff;
        }
        this.plane = new THREE.Mesh(geometry, new THREE.MeshBasicMaterial({ color: color}));
        this.objects.push(this.plane);
        this.scene.add(this.plane);


    }
    initAxes(args) {
        if (!args) return;
        this.axesHelper = new THREE.AxesHelper(args);
        this.scene.add(this.axesHelper);
    }
    initGrid(args = {}) {
        if (args === null) args = {};
        let { enable = false, w, n, cc, gc, opacity} = args;
        if (this.grid) this.scene.remove(this.grid);
        // console.log('------> ', args, enable);
        if (!enable) return (this.grid = null);

        this.grid = new THREE.GridHelper(w, n, cc, gc);
        this.grid.material.opacity = opacity ? opacity : 0.1;
        this.grid.material.depthWrite = false;
        this.grid.material.transparent = true;
        this.scene.add(this.grid);
    }
    initControls() {
        // console.log('Director.initControls: ', this);
        let dom = this.root.renderer.domElement,
            viewType = Storage.getSessionStorage('TINYENGINE').get('SETTINGS').viewType;
        if (this.root.labelRenderer) dom = this.root.labelRenderer.domElement;
        this.controls = new OrbitControls(this.camera, dom);
        // this.controls.up.set(0, 1, 0);
        this.controls.enableDamping = true; // an animation loop is required when either damping or auto-rotation are enabled
        this.controls.damping = 0.05;
        // this.controls.screenSpacePanning = true;
        this.controls.target.set(0, 0, 0);
        // if (viewType === '2D') {
        //     // 放大和缩小的距离,仅限正交照相机
        //     this.controls.minZoom = 0.5;
        //     this.controls.maxZoom = 4;
        // } else {
        //     // 可以推拉进出多远，仅限透视相机
        //     this.controls.minDistance = 10;
        //     this.controls.maxDistance = 800;
        // }
        // this.controls.minAzimuthAngle = 0;
        // this.controls.maxAzimuthAngle = 0;
        this.controls.minPolarAngle = (Math.PI / 180) * 0;
        this.controls.maxPolarAngle = (Math.PI / 180) * 90;
        // this.controls.update();
        // this.controls.enableZoom = false; //  Disable the original method, customize the zoom where the mouse on.
        // this.controls.addEventListener('end', this.mouseWheelHandler.bind(this));
        // return this.controls;
        this.controls.screenSpacePanning = false;

        this.controls.enableRotate = viewType !== '2D'; //eslint-disable-line
    }
    initPointerSprite() {
        this.spriteGroup = new THREE.Group();
        this.spriteGroup.rotation.x = -Math.PI / 2;

        // console.log('Director.initPointerSprite: ');
        let sprite = new Sprite('pointer');
        this.spriteY = sprite.getTextSprite('Y');
        this.spriteY.rotation.x = -Math.PI / 2;
        this.spriteY.position.set(0, 5, 0);
        this.spriteGroup.add(this.spriteY);

        this.spriteX = sprite.getTextSprite('X');
        this.spriteX.rotation.x = -Math.PI / 2;
        this.spriteX.position.set(5, 0, 0);
        this.spriteGroup.add(this.spriteX);
        // this.pointerSprite.center = new THREE.Vector2(0, 0);
        // this.currentScene.add(this.pointerSprite);
        if (this.currentScene) this.currentScene.add(this.spriteGroup);
    }
    initAnimationMixer(model) {
        this.mixer = new THREE.AnimationMixer(model);
        return this.mixer;
    }
    initEffect(args) {
        if (!args) return (this.composer = null);
        let {
            // enable = false,
            width = this.viewport.width,
            height = this.viewport.height,
            exposure = 1,
            bloomStrength = 0, // It will decided bloomPass enable or not.
            bloomThreshold = 0.25,
            bloomRadius = 0.8,
            edgeStrength = 0, // It will decided outlinePass enable or not.
            edgeGlow = 0.1,
            edgeThickness = 1,
            pulsePeriod = 1,
            usePatternTexture = false,
            visibleEdgeColor = '#ffffff',
            hiddenEdgeColor = '#190a05'
        } = args;

        // if (!enable) return (this.composer = null);
        // if (!bloomThreshold && !edgeStrength) return (this.composer = null);
        if (!bloomStrength && !edgeStrength) return (this.composer = null);

        this.root.renderer.toneMappingExposure = Math.pow(exposure, 4.0);

        let bloomPass = new UnrealBloomPass(new THREE.Vector2(width, height), 1.5, 0.4, 0.85);
        bloomPass.threshold = bloomThreshold;
        bloomPass.strength = bloomStrength;
        bloomPass.radius = bloomRadius;

        this.outlinePass = new OutlinePass(new THREE.Vector2(this.viewport.width, this.viewport.height), this.currentScene, this.camera); //eslint-disable-line
        this.outlinePass.edgeStrength = edgeStrength;
        this.outlinePass.edgeGlow = edgeGlow;
        this.outlinePass.edgeThickness = edgeThickness;
        this.outlinePass.pulsePeriod = pulsePeriod;
        this.outlinePass.usePatternTexture = usePatternTexture;
        this.outlinePass.visibleEdgeColor.set(visibleEdgeColor);
        this.outlinePass.hiddenEdgeColor.set(hiddenEdgeColor);

        // this.scene.userData.outlineEnabled = true;

        this.composer = new EffectComposer(this.root.renderer);
        let renderPass = new RenderPass(this.scene, this.camera);
        this.composer.addPass(renderPass);
        if (bloomThreshold) this.composer.addPass(bloomPass);
        if (edgeStrength) this.composer.addPass(this.outlinePass);
    }

    resetCamera() {
        this.camera.position.set(this.defaultCamera.x, this.defaultCamera.y, this.defaultCamera.z);
        this.scene.rotation.x = 0;
        this.scene.rotation.y = 0;
        this.scene.rotation.z = 0;
        this.camera.lookAt(0, 0, 0);
    }
    moveCamera({ x, y, z, lx, ly, lz } = {}) {
        this.scene.rotation.set(0, 0, 0);
        if (this.controls) this.controls.target.set(lx, ly, lz);
        this.camera.lookAt(0, 0, 0);
        this.cameraDestination = { x, y, z };
    }

    pushScene(scene) {
        this.currentScene = scene;
    }
    getCurrentScene() {
        if (!this.currentScene) this.currentScene = this.initScene();
        return this.currentScene;
    }

    animationMixerChanged(mixer) {
        if (!mixer) return;
        this.mixer = mixer;
    }

    topViewChanged(res) {  //eslint-disable-line
        let settings = _.cloneDeep(Storage.getSessionStorage('TINYENGINE').get('SETTINGS'));
        if (settings.viewType === '2D') settings.viewType = '3D';
        else settings.viewType = '2D';
        Storage.getSessionStorage('TINYENGINE').set('SETTINGS', settings);
        // console.error(this.camera);
        // if (!this.camera) return;
        // if (res) this.camera.position.set(0, 500, 0);
        // else this.camera.position.set(this.defaultCamera.x, this.defaultCamera.y, this.defaultCamera.z);
        // this.camera.lookAt(new THREE.Vector3(0, 0, 0));
        // this.initCamera();
        // if (!this.controls) return;
        // this.controls.target.set(0, 0, 0);
        // this.controls.enableRotate = !res;
    }
    rotatableChanged(tag, val) {
        if (tag && val) this.rotationSpeed = val;
        else this.rotationSpeed = 0;
    }
    enableRotate(val) {
        if (!this.controls) return;
        this.controls.enableRotate = val;
    }

    running() {
        let step = { x: 0, y: 0, z: 0 },
            d = 2;
        if (!this.camera || !this.camera.position || !this.cameraDestination) return;
        if (this.camera.position.x - this.cameraDestination.x > d) step.x = -d;
        else if (this.camera.position.x - this.cameraDestination.x < -d) step.x = d;
        if (this.camera.position.y - this.cameraDestination.y > d) step.y = -d;
        else if (this.camera.position.y - this.cameraDestination.y < -d) step.y = d;
        if (this.camera.position.z - this.cameraDestination.z > d) step.z = -d;
        else if (this.camera.position.z - this.cameraDestination.z < -d) step.z = d;
        this.camera.position.set(
            this.camera.position.x + step.x,
            this.camera.position.y + step.y,
            this.camera.position.z + step.z
        );
        if (step.x === 0 && step.y === 0 && step.z === 0) this.cameraDestination = null;
    }
}

export default Artist;
